home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Risc World 5
/
Risc World 5.iso
/
SOFTWARE
/
Issue5
/
PD
/
DIRSYNC
/
LegalStuff
/
bascat
/
bascat.c
next >
Wrap
C/C++ Source or Header
|
2002-11-24
|
20KB
|
781 lines
/* -*-c-*-
*
* $Id: bascat.c,v 1.1 1997/07/23 01:19:33 mdw Exp mdw $
*
* Display BBC BASIC programs more or less anywhere
*
* (c) 1996, 1997 Matthew Wilcox and Mark Wooding
* (c) 1999 Darren Salt
*/
/*----- Licensing notice --------------------------------------------------*
*
* This file is part of Bascat.
*
* Bascat is free software; you can redistribute it and/or modify it
* under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* Bascat is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Bascat; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*----- Revision history --------------------------------------------------*
*
* Revision 1.2.1 1999/06/19 ds
* Compilable under RISC OS (using shared C library & libgnu); no -p, -l.
* Various other things :-)
*
* $Log: bascat.c,v $
* Revision 1.1 1997/07/23 01:19:33 mdw
* Initial revision
*
*/
/*----- Header files ------------------------------------------------------*/
/* --- ANSI library headers --- */
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* --- GNU library headers --- */
#include <error.h>
/* --- Operating system specific headers --- */
#ifdef HAVE_LIBTERMCAP
# include <termcap.h>
#endif
#ifdef USING_ACORN_SHAREDCLIB
/* also libgnu <URL:ftp://ftp.zap.uk.eu.org/pub/ds/GNU/src/libgnu.zip> */
# include "system.h"
# include "ro_wild.h"
#else
# include <unistd.h>
#endif
/* --- Private headers --- */
#include "mdwopt.h"
/*----- Version information -----------------------------------------------*/
#ifndef NDEBUG
# define D(x) x
#else
# define D(x)
#endif
/*----- Tokenisation tables -----------------------------------------------*
*
* These tables are from the BBC BASIC guide. Additional verification
* carried out on an A440 with RISC OS 3.1
*/
static const char *bcTok__base[] = {
"OTHERWISE",
"AND", "DIV", "EOR", "MOD", "OR", "ERROR", "LINE", "OFF",
"STEP", "SPC", "TAB(", "ELSE", "THEN", "*", "OPENIN", "PTR",
"PAGE", "TIME", "LOMEM", "HIMEM", "ABS", "ACS", "ADVAL", "ASC",
"ASN", "ATN", "BGET", "COS", "COUNT", "DEG", "ERL", "ERR",
"EVAL", "EXP", "EXT", "FALSE", "FN", "GET", "INKEY", "INSTR(",
"INT", "LEN", "LN", "LOG", "NOT", "OPENUP", "OPENOUT", "PI",
"POINT(", "POS", "RAD", "RND", "SGN", "SIN", "SQR", "TAN",
"TO", "TRUE", "USR", "VAL", "VPOS", "CHR$", "GET$", "INKEY$",
"LEFT$(", "MID$(", "RIGHT$(", "STR$", "STRING$(", "EOF", "*", "*",
"*", "WHEN", "OF", "ENDCASE", "ELSE", "ENDIF", "ENDWHILE", "PTR",
"PAGE", "TIME", "LOMEM", "HIMEM", "SOUND", "BPUT", "CALL", "CHAIN",
"CLEAR", "CLOSE", "CLG", "CLS", "DATA", "DEF", "DIM", "DRAW",
"END", "ENDPROC", "ENVELOPE", "FOR", "GOSUB", "GOTO", "GCOL", "IF",
"INPUT", "LET", "LOCAL", "MODE", "MOVE", "NEXT", "ON", "VDU",
"PLOT", "PRINT", "PROC", "READ", "REM", "REPEAT", "REPORT", "RESTORE",
"RETURN", "RUN", "STOP", "COLOUR", "TRACE", "UNTIL", "WIDTH", "OSCLI"
};
static const char *bcTok__c6[] = {
"SUM", "BEAT"
};
static const char *bcTok__c7[] = {
"APPEND", "AUTO",
"CRUNCH", "DELETE", "EDIT", "HELP", "LIST", "LOAD", "LVAR", "NEW",
"OLD", "RENUMBER", "SAVE", "TEXTLOAD", "TEXTSAVE", "TWIN", "TWINO",
"INSTALL"
};
static const char *bcTok__c8[] = {
"CASE", "CIRCLE",
"FILL", "ORIGIN", "POINT", "RECTANGLE", "SWAP", "WHILE", "WAIT", "MOUSE",
"QUIT", "SYS", "INSTALL", "LIBRARY", "TINT", "ELLIPSE", "BEATS", "TEMPO",
"VOICES", "VOICE", "STEREO", "OVERLAY"
};
#define ITEMS(array) (sizeof(array) / sizeof((array)[0]))
/*----- Static variables --------------------------------------------------*/
enum {
s_keyword, /* Expecting a keyword next */
s_normal, /* Normal state, reading input */
s_comment, /* In a command (or literal *cmd) */
s_quote, /* Inside a quoted string */
s_8d, /* Inside an embedded line number */
s_c6, s_c7, s_c8, /* Various shift states */
s_dummy
};
static char bc__state = s_normal; /* Current detokenisation state */
static long bc__lineno; /* The bytes representing the line number */
static long bc__lineno_count; /* The number of bytes unread */
enum {
f_goto_harmful = 1, /* GOTO etc. considered harmful */
f_leave_tokenised = 2, /* Don't detokenise (except line nos) */
f_linenumbers = 4, /* Display linenumbers on left */
f_many_files = 1<<8, /* More than one file given */
f_lineno_warn = 1<<9, /* We've encountered a line number */
#ifdef __riscos
f_allow_ctrl = 1<<16, /* Don't complain about ctrl chars */
#else
f_highlight = 1<<16, /* Highlight keywords and things */
f_tty = 1<<17, /* We're writing to TTY (or pager) */
f_less = 1<<18, /* We're piping through `less' */
#endif
f_dummy
};
static int bc__flags; /* Various options flags */
#ifdef HAVE_LIBTERMCAP
static char bc__termcap[2048]; /* Terminal capabilities buffer */
#endif
#ifndef __riscos
static char *bc__pager = 0; /* Pointer to pager to use */
#endif
const char *program_name = 0; /* Program name (for GNU error()) */
/*----- Main code ---------------------------------------------------------*/
/* --- @bc__badprogram@ --- *
*
* Arguments: @const char *msg@ = the error message
* @const char *name@ = the file providing the input stream
*
* Returns: 1
*
* Use: Displays a "bad program" error via GNU error().
*/
static int bc__badprogram(const char *msg, const char *name)
{
if (name)
error(0, 0, "%s: bad program: %s", name, msg);
else
error(0, 0, "bad program: %s", msg);
return 1;
}
/* --- @bc__programerror@ --- *
*
* Arguments: @const char *msg@ = the error message
* @const char *name@ = the file providing the input stream
*
* Returns: 1
*
* Use: Displays a general error message via GNU error().
*/
static int bc__programerror(const char *msg, const char *name)
{
if (name)
error(0, 0, "%s: %s", name, msg);
else
error(0, 0, "%s", msg);
return 1;
}
/* --- @bc__keyword@ --- *
*
* Arguments: @char *s@ = pointer to keyword string
* @FILE *fp@ = stream to write onto
*
* Returns: --
*
* Use: Displays a keyword in a nice way. There's some nasty hacking
* here to make GNU's `less' work properly. `more' appears to
* cope with highlighting codes OK, so that's fine. `less'
* prefers it if we attempt to `overstrike' the bolded
* characters. What fun...
*/
static void bc__keyword(const char *s, FILE *fp)
{
#ifdef HAVE_LIBTERMCAP
if ((~bc__flags & (f_less | f_highlight)) == 0) {
while (*s) {
putc(*s, fp);
putc(8, fp); /* evil... */
putc(*s, fp);
s++;
}
} else {
static char buff[24];
static char *hs, *he, *p = buff;
if (!hs) {
if (bc__flags & f_highlight) {
hs = tgetstr("md", &p);
he = tgetstr("me", &p);
} else
hs = he = "";
}
fputs(hs, fp);
fputs(s, fp);
fputs(he, fp);
}
#else
fputs(s, fp);
#endif
}
/* --- @bc__mbtok@ --- *
*
* Arguments: @int byte@ = the current byte
* @const char *t[]@ = pointer to token table
* @int n@ = number of items in token table
* @FILE *fp@ = stream to write onto
* @const char *name@ = the file providing the input stream
*
* Returns: 0 if everything's OK
*
* Use: Decodes multibyte tokens.
*/
static int bc__mbtok(int byte, const char *t[], int n, FILE * fp,
const char *name)
{
byte -= 0x8E;
if (byte >= n)
return bc__badprogram("invalid multibyte token", name);
bc__keyword(t[byte], fp);
bc__state = s_normal;
return (0);
}
/* --- @bc__decode@ --- *
*
* Arguments: @int byte@ = byte to decode
* @FILE *fp@ = stream to write onto
* @const char *name@ = the file providing the input stream
*
* Returns: 0 if everything's going OK
*
* Use: Decodes a byte, changing states as necessary.
*/
static int bc__decode(int byte, FILE * fp, const char *name)
{
switch (bc__state) {
case s_keyword:
if (byte == '*')
bc__state = s_comment;
else
bc__state = s_normal;
/* Fall through here */
case s_normal:
#ifdef __riscos
if (bc__flags && f_allow_ctrl) {
if (byte == '\n')
return bc__programerror("sorry, cannot handle embedded line feeds", name);
} else
#endif
if (byte < 0x20)
return bc__programerror("sorry, cannot handle embedded control characters", name);
if (byte >= 0x7F) {
switch (byte) {
case 0x8D:
if (!(bc__flags & (f_linenumbers | f_lineno_warn))) {
error (0, 0, "%sfound a line number token",
bc__flags & f_goto_harmful ? "" : "warning: ");
if (bc__flags & f_goto_harmful)
return 1;
bc__flags |= f_lineno_warn;
}
bc__state = s_8d;
bc__lineno = 0;
bc__lineno_count = 0;
break;
case 0xC6:
bc__state = s_c6;
break;
case 0xC7:
bc__state = s_c7;
break;
case 0xC8:
bc__state = s_c8;
break;
case 0x8B: /* ELSE */
case 0x8C: /* THEN */
case 0xF5: /* REPEAT (a funny one) */
bc__state = s_keyword;
bc__keyword(bcTok__base[byte - 0x7F], fp);
break;
case 0xE4: /* GOSUB */
if (bc__flags & f_goto_harmful)
return bc__programerror("found a GOSUB", name);
goto default_action;
case 0xE5: /* GOTO */
if (bc__flags & f_goto_harmful)
return bc__programerror("found a GOTO", name);
goto default_action;
case 0xDC: /* DATA */
case 0xF4: /* REM */
bc__state = s_comment;
/* Fall through here */
default:
default_action:
if (bc__flags & f_leave_tokenised)
fputc(byte, fp);
else
bc__keyword(bcTok__base[byte - 0x7F], fp);
break;
}
} else {
if (byte == '"')
bc__state = s_quote;
fputc(byte, fp);
}
break;
case s_quote:
if (byte == '"')
bc__state = s_normal;
/* Fall through here */
case s_comment:
fputc(byte, fp);
break;
case s_8d:
bc__lineno |= byte << bc__lineno_count;
bc__lineno_count += 8;
if (bc__lineno_count == 24) {
int i = (bc__lineno >> 8) & 0x3F3F;
int j = bc__lineno ^ 0x54;
i |= (j & 0x30) << 2;
i |= (j & 0xC) << 12;
i |= (j & 3) << 16; /* hmm... */
fprintf (fp, "%i", i);
bc__state = s_normal;
}
break;
case s_c6:
if (bc__flags & f_leave_tokenised) {
fputc(0xC6, fp);
fputc(byte, fp);
return 0;
} else
return (bc__mbtok(byte, bcTok__c6, ITEMS(bcTok__c6), fp, name));
break;
case s_c7:
if (bc__flags & f_leave_tokenised) {
fputc(0xC7, fp);
fputc(byte, fp);
return 0;
} else
return (bc__mbtok(byte, bcTok__c7, ITEMS(bcTok__c7), fp, name));
break;
case s_c8:
if (bc__flags & f_leave_tokenised) {
fputc(0xC8, fp);
fputc(byte, fp);
return 0;
} else
return (bc__mbtok(byte, bcTok__c8, ITEMS(bcTok__c8), fp, name));
break;
}
return (0);
}
/* --- @bc__line@ --- *
*
* Arguments: @FILE *in@ = input stream to read
* @FILE *out@ = output stream to write
* @const char *name@ = the file providing the input stream
*
* Returns: 0 if there's another line after this one.
* -1 if this was the last line.
* 1 if there was a format error (bad program etc.).
*
* Use: Decodes a BASIC line into stuff to be written.
*/
static int bc__line(FILE *in, FILE *out, const char *name)
{
/* --- Read the line number --- */
{
int a, b;
a = getc(in);
D( fprintf(stderr, "ln_0 == %i\n", a); )
if (a == EOF)
goto eof;
if (a == 0xFF)
return (-1);
b = getc(in);
D( fprintf(stderr, "ln_1 == %i\n", b); )
if (b == EOF)
goto eof;
if (bc__flags & f_linenumbers)
fprintf(out, "%5i", (a << 8) + b);
}
{
int len;
int byte;
len = getc(in);
D( fprintf(stderr, "linelen == %i\n", len); )
if (len == EOF)
goto eof;
len -= 4;
bc__state = s_normal;
while (len) {
byte = getc(in);
D( fprintf(stderr, "state == %i, byte == %i\n", \
bc__state, byte); )
if (byte == EOF)
goto eof;
if (bc__decode(byte, out, name))
return 1;
len--;
}
putc('\n', out);
byte = getc(in);
D( fprintf(stderr, "eol == %i\n", byte); )
if (byte == EOF)
goto eof;
else if (byte != 0x0D)
return bc__badprogram("expected end-of-line delimiter", name);
}
return (0);
eof:
return bc__badprogram("unexpected end-of-file", name);
}
/* --- @bc__file@ --- *
*
* Arguments: @FILE *in@ = the input stream
* @FILE *out@ = the output stream
* @const char *name@ = the file providing the input stream
*
* Returns: 0 if OK
*
* Use: Decodes an entire file.
*/
static int bc__file(FILE *in, FILE *out, const char *name)
{
int byte;
if (bc__flags & f_many_files)
fprintf(out, "%sREM >%s\n", (bc__flags & f_linenumbers) ? " 0" : "",
name);
/* --- Check for the inital newline char --- */
byte = getc(in);
if (byte != 0x0D)
return bc__badprogram("doesn't start with a newline", name);
/* --- Now read the lines in one by one --- */
while ((byte = bc__line(in, out, name)) == 0) ;
if (byte == 1)
return 1; /* if an error was reported by bc__line */
/* --- Check that we're really at end-of-file --- */
byte = getc(in);
if (byte != EOF)
bc__programerror("warning: found data after end of program", name);
return 0;
}
/* --- @bc__sigPipe@ --- *
*
* Arguments: @int s@ = signal number
*
* Returns: Doesn't
*
* Use: Handles SIGPIPE signals, and gracefully kills the program.
*/
#if defined SIGPIPE && !defined __riscos
static void bc__sigPipe(int s)
{
(void) s;
exit(0); /* Gracefully, oh yes */
}
#endif
/* --- @bc__options@ --- *
*
* Arguments: @int c@ = number of arguments
* @char *v[]@ = pointer to arguments
* @char *s@ = pointer to short options
* @struct option *o@ = pointer to long options
*
* Returns: --
*
* Use: Parses lots of arguments.
*/
static void bc__options(int c, char *v[], const char *s,
const struct option *o)
{
int i;
for (;;) {
i = mdwopt(c, v, s, o, 0, 0, gFlag_negation | gFlag_envVar);
if (i == -1)
break;
switch (i) {
case 'v':
case 'h':
printf("%s v. " VERSION " (" __DATE__ ")\n", program_name);
if (i == 'v')
exit(0);
printf("\n"
#ifdef __riscos
"Usage: %s [OPTION]... [FILE]...\n"
#else
"Usage: %s [OPTION]... [FILE]...\n"
#endif
"\n"
"Types BBC BASIC programs in a readable way. Options currently supported are as\n"
"follows:\n"
"\n"
" -n, --line-numbers displays line numbers for each line\n"
" -g, --goto-harmful error if GOTO, GOSUB or a line number is used\n"
" -t, --leave-as-tokens don't detokenise (except for line numbers)\n"
#ifdef __riscos
" -c, --allow-ctrl-chars don't complain about most embedded control codes\n"
#else
" -l, --highlight attempts to highlight keywords\n"
" -p, --pager=PAGER sets pager to use (default $PAGER)\n"
#endif
" -h, --help displays this help text\n"
" -v, --version displays the current version number\n"
"\n"
"Prefix long options with `no-' to cancel them. Use `+' to cancel short options.\n"
"Options can also be placed in the `BASCAT' environment variable, if you don't\n"
"like the standard settings.\n",
program_name);
exit(0);
break;
case 'n':
bc__flags |= f_linenumbers;
break;
case 'n' | gFlag_negated:
bc__flags &= ~f_linenumbers;
break;
case 'g':
bc__flags |= f_goto_harmful;
break;
case 'g' | gFlag_negated:
bc__flags &= ~f_goto_harmful;
break;
case 't':
bc__flags |= f_leave_tokenised;
break;
case 't' | gFlag_negated:
bc__flags &= ~f_leave_tokenised;
break;
#ifdef __riscos
case 'c':
bc__flags |= f_allow_ctrl;
break;
case 'c' | gFlag_negated:
bc__flags &= ~f_allow_ctrl;
break;
#else
case 'l':
bc__flags |= f_highlight;
break;
case 'l' | gFlag_negated:
bc__flags &= ~f_highlight;
break;
case 'p':
bc__pager = optarg;
break;
#endif
}
}
}
/* --- @main@ --- *
*
* Arguments: @int argc@ = number of arguments
* @char *argc[]@ = pointer to what the arguments are
*
* Returns: 0 if it all worked
*
* Use: Displays BASIC programs.
*/
int main(int argc, char *argv[])
{
static struct option opts[] = {
{ "help", 0, 0, 'h' },
{ "version", 0, 0, 'v' },
{ "line-numbers", gFlag_negate, 0, 'n' },
{ "goto-harmful", gFlag_negate, 0, 'g' },
{ "leave-as-tokens", gFlag_negate, 0, 't' },
#ifdef __riscos
{ "allow-ctrl-chars", gFlag_negate, 0, 'c' },
#else
{ "highlight", gFlag_negate, 0, 'l' },
{ "pager", gFlag_argReq, 0, 'p' },
#endif
{ 0, 0, 0, 0 }
};
#ifdef __riscos
static char *shortopts = "hvn+g+t+c+";
#else
static char *shortopts = "hvn+g+t+l+p:";
#endif
int ret = 0;
program_name = argv[0];
/* --- Parse the command line options --- */
bc__options(argc, argv, shortopts, opts);
/* --- Now do the job --- */
if (optind == argc && isatty(0)) {
fprintf(stderr,
"%s: no filenames given, and standard input is a tty\n"
"To force reading from stdin, use `%s -'. For help, type "
"`%s --help'.\n",
program_name, program_name, program_name);
exit(0);
}
#ifdef HAVE_LIBTERMCAP
if (bc__flags & f_highlight)
tgetent(bc__termcap, getenv("TERM"));
#endif
{
FILE *in;
#ifdef __riscos
FILE *out = stdout;
#else
FILE *out = 0;
/* --- If output is to a terminal, try paging --- *
*
* All programs which spew text should do this ;-)
*/
if (isatty(1)) {
if (!bc__pager)
bc__pager = getenv("PAGER");
if (!bc__pager)
bc__pager = PAGER; /* Worth a try */
if (bc__pager) { /* Allow sensible behaviour if no pager */
if (strstr(bc__pager, "less"))
bc__flags |= f_less; /* HACK!!! */
out = popen(bc__pager, "w");
}
if (!out)
out = stdout;
else {
bc__flags |= f_tty;
# ifdef SIGPIPE
signal(SIGPIPE, bc__sigPipe);
# endif
}
} else
out = stdout;
#endif
/* --- Now go through all the files --- */
if (argc - optind > 1)
bc__flags |= f_many_files;
if (optind == argc)
bc__file(stdin, out, "(standard input)");
else
while (optind < argc) {
#ifdef __riscos
if (ro_wild_test(argv[optind])) {
ro_wild *wild = ro_wild_init(argv[optind]);
char *name;
bc__flags |= f_many_files;
while ((name = ro_wild_next(wild)) != 0) {
in = fopen(name, "rb");
if (!in) {
error(0, errno, "%s: couldn't open", name);
ret = 1;
} else {
ret |= bc__file(in, out, name);
fclose(in);
}
}
if (!ro_wild_finish(wild)) {
error(0, 0, "%s: couldn't open", argv[optind]);
ret = 1;
}
} else
#endif
if (strcmp(argv[optind], "-") == 0)
bc__file(stdin, out, "(standard input)");
else {
in = fopen(argv[optind], "rb");
if (!in) {
error(0, errno, "%s: couldn't open", argv[optind]);
ret = 1;
} else {
ret |= bc__file(in, out, argv[optind]);
fclose(in);
}
}
optind++;
}
#ifndef __riscos
if (bc__flags & f_tty)
pclose(out);
#endif
}
return (0);
}
/*----- That's all, folks -------------------------------------------------*/